﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Security.Cryptography;
using System.IO;

namespace McSherry.SXM.Base.FileFormat
{
    /// <summary>
    /// A class for encrypting SX files using the AES 256-bit cipher.
    /// </summary>
    public class AES
    {
        internal byte[] Key = new byte[32];
        internal byte[] Vector = new byte[16];

        private ICryptoTransform EncryptorTransform, DecryptorTransform;
        private UTF8Encoding UTFEncoder;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="dk">The developer key to be used with encryption.</param>
        public AES(DeveloperKey dk)
        {
            RijndaelManaged rm = new RijndaelManaged();
            EncryptorTransform = rm.CreateEncryptor(this.Key, this.Vector);
            DecryptorTransform = rm.CreateDecryptor(this.Key, this.Vector);

            UTFEncoder = new UTF8Encoding();

            for (int i = 0; i < 32; i++)
                this.Key[i] = dk.Key[i];
            for (int i = 32, b = 0; i < 48; i++, b++)
                this.Vector[b] = dk.Key[i];
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="dk">The developer key to be used with encryption.</param>
        public AES(DeveloperKey? dk) { }

        /// <summary>
        /// Decrypt a string value.
        /// </summary>
        /// <param name="encstr">The ciphertext input.</param>
        /// <returns>The decrypted value of the string.</returns>
        public string DecryptString(string encstr)
        {
            return Decrypt(StrToByteArray(encstr));
        }
        /// <summary>
        /// Encrypt a value to a string.
        /// </summary>
        /// <param name="strval">The plaintext input.</param>
        /// <returns>A ciphertext created from the plaintext using the provided key.</returns>
        public string EncryptToString(string strval)
        {
            return ByteArrToString(Encrypt(strval));
        }

        /// <summary>
        /// Encrypt a plaintext value.
        /// </summary>
        /// <param name="textval">The plaintext value.</param>
        /// <returns>A ciphertext value that is the encrypted plaintext.</returns>
        public byte[] Encrypt(string textval)
        {
            byte[] bytes = UTFEncoder.GetBytes(textval);

            MemoryStream ms = new MemoryStream();
            
            CryptoStream cs = new CryptoStream(ms, EncryptorTransform, CryptoStreamMode.Write);
            cs.Write(bytes, 0, bytes.Length);
            cs.FlushFinalBlock();

            ms.Position = 0;
            byte[] enc = new byte[ms.Length];
            ms.Read(enc, 0, enc.Length);

            cs.Close();
            ms.Close();

            return enc;
        }
        /// <summary>
        /// Decrypt a ciphertext value.
        /// </summary>
        /// <param name="encval">The ciphertext value.</param>
        /// <returns>A plaintext value that is the decrypted ciphertext.</returns>
        public string Decrypt(byte[] encval)
        {
            MemoryStream ms = new MemoryStream();
            CryptoStream cs = new CryptoStream(ms, DecryptorTransform, CryptoStreamMode.Write);
            cs.Write(encval, 0, encval.Length);
            cs.FlushFinalBlock();

            ms.Position = 0;
            byte[] decbytes = new byte[ms.Length];
            ms.Read(decbytes, 0, decbytes.Length);
            ms.Close();

            return UTFEncoder.GetString(decbytes);
        }

        /// <summary>
        /// Converts a string to a byte array.
        /// </summary>
        /// <param name="str">The string to convert.</param>
        /// <returns>A byte array where each byte represents a character in the string.</returns>
        public byte[] StrToByteArray(string str)
        {
            if (str.Length == 0)
                throw new Exception("Invalid string value in StrToByteArray");

            byte val;
            byte[] byteArr = new byte[str.Length / 3];
            int i, j;
            i = j = 0;

            do
            {
                val = byte.Parse(str.Substring(i, 3));
                byteArr[j++] = val;
                i += 3;
            } while (i < str.Length);

            return byteArr;
        }
        /// <summary>
        /// Converts a byte array into its string representation.
        /// </summary>
        /// <param name="byteArr">The byte array to convert.</param>
        /// <returns>A string made from each byte in the array.</returns>
        public string ByteArrToString(byte[] byteArr)
        {
            string tempStr = "";
            for (int i = 0; i <= byteArr.GetUpperBound(0); i++)
            {
                tempStr += Encoding.ASCII.GetString(new byte[] { byteArr[i] });
            }
            return tempStr;
        }
    }

    /// <summary>
    /// A 384-bit developer key to be used with the SX file format.
    /// </summary>
    public struct DeveloperKey
    {
        /// <summary>
        /// The 384-bit key.
        /// </summary>
        public byte[] Key;
        /// <summary>
        /// Whether the instance of the struct has generated a key.
        /// </summary>
        public bool hasGenerated;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="genkey">Whether a key should be generated on instantiation.</param>
        public DeveloperKey(bool genkey)
        {
            this.Key = new byte[48];
            if (genkey)
            {
                this.hasGenerated = true;
                this.GenerateKey();
            }
            else this.hasGenerated = false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="DevKeyB64">A Base64 representation of the key to represent.</param>
        public DeveloperKey(string DevKeyB64)
        {
            byte[] dkey = Convert.FromBase64String(DevKeyB64);

            if (dkey.Length != 48)
                throw new ArgumentOutOfRangeException(
                    "DevKeyB64", "Developer key must be 64 bytes in length.");
            else
            {
                this.Key = dkey;
                this.hasGenerated = true;
            }
        }

        /// <summary>
        /// A DeveloperKey with its Key set to null.
        /// </summary>
        public static DeveloperKey NullKey
        {
            get
            {
                return new DeveloperKey()
                {
                    Key = null
                };
            }
        }

        /// <summary>
        /// Generates a new key.
        /// </summary>
        public void GenerateKey()
        {
            uint counter = 0;
            foreach (byte b in Key)
            {
                Key[counter] = byte.Parse((new Random().Next(255) - counter).ToString());
                counter++;
            }
        }
        /// <summary>
        /// Converts the current key value to a Base64 string.
        /// </summary>
        /// <returns>A Base64 representation of the key.</returns>
        public string ToBase64String()
        {
            return Convert.ToBase64String(this.Key);
        }

        public static bool operator ==(DeveloperKey D1, DeveloperKey D2)
        {
            if (D1.Key == D2.Key)
                return true;
            else return false;
        }
        public static bool operator !=(DeveloperKey D1, DeveloperKey D2)
        {
            if (D1.Key != D2.Key)
                return true;
            else return false;
        }
        /// <summary>
        /// Checks whether the provided object is equal to this instance of the DeveloperKey.
        /// </summary>
        /// <param name="obj">The object to compare.</param>
        /// <returns>Whether the provided object is equal to this instance of the DeveloperKey.</returns>
        public override bool Equals(object obj)
        {
            if (((DeveloperKey)obj).Key == this.Key)
                return true;
            else return false;
        }
    }
}
